/*
Copyright (C) 2006-2007 The OZI Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows 
these conditions in the documentation and/or other materials 
provided with the distribution.
3. The name "OZI" must not be used to endorse or promote products
derived from this software without prior written permission.  For
written permission, please contact The OZI Project.
4. Products derived from this software may not be called "OZI", nor
may "OZI" appear in their name, without prior written permission
from The OZI Project.
In addition, we request (but do not require) that you include in the 
end-user documentation provided with the redistribution and/or in the 
software itself an acknowledgement equivalent to the following:
"This product includes software developed by The OZI Project."
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
This software consists of voluntary contributions made by many 
individuals on behalf of The OZI Project and was originally 
created by Matt Jezorek <mjezorek@bellsouth.net>.  
 */
package com.ozi.core;

import org.apache.log4j.Logger;
import com.ozi.utils.*;
import java.util.HashMap;
import java.util.List;

/**
 *
 * @author Matt
 */
public class OZI {

    private static Logger log = Logger.getLogger(OZI.class);
    private String uri;
    private static HashMap<String, OZI> zones = new HashMap<String, OZI>();
    private String zoneID;

    public OZI(String URI) {
        Configurator.createConfig();
        this.uri = URI;
    }

    public SIFMessage sendMessage(SIFMessage message) throws Exception {
        OZIConnection conn = OZIConnectionFactory.getConnection(uri);
        SIFMessage response = null;
        try {
            if (Configurator.get(Constants.DEBUG_SIF) == "true") {
                log.debug("sending message");
                OZIMarshaller marshall = OZIMarshaller.getInstance();
                marshall.enableFormatedOutput();
                System.out.println("========= REQUEST ==========");
                marshall.marshal(message, System.out);
            }
            response = conn.sendMessage(message);
            if (Configurator.get(Constants.DEBUG_SIF) == "true") {
                log.debug("response received");
                OZIMarshaller marshall = OZIMarshaller.getInstance();
                marshall.enableFormatedOutput();
                System.out.println("========= RESPONSE ===========");
                marshall.marshal(response, System.out);
            }
        } catch (Exception e) {
            log.error("error while sending message", e);
            throw (e);
        }

        return (response);
    }

    /**
     * Returns the URI used to communicated with this endpoint
     * 
     * @return the uri
     */
    public String getUri() {
        return uri;
    }

    /**
     * Sets the URI used to communicate with ths endpoint
     * 
     * @param uri
     *            the uri to set
     */
    public void setUri(String uri) {
        this.uri = uri;
    }

    /**
     * Creates a new OZI abstraction / Factory method
     * Zones are created only once and from that point on they are returned from cache
     *
     * @param zoneID the name/id that you will use to reference this zone 
     * @param uri the uri of the SIF Zone
     * @return a new OZI object
     *
     */
    public static OZI createOZI(String zoneID, String uri) {
        if (!zones.containsKey(zoneID)) {
            log.info(String.format("creating new zone: [%s]", zoneID));
            zones.put(zoneID, new OZI(zoneID, uri));
        }

        OZI z = zones.get(zoneID);

        log.debug("returning zone " + zoneID);
        return (z);
    }

    private OZI(String zoneID, String uri) {
        this.zoneID = zoneID;
        log.info(String.format("OZI init [%s]", zoneID));
        if (Configurator.get(Constants.AUTO_REGISTER).contains("true")) {
            log.info("automatically registering agent");
            try {
                register();
            } catch (Exception e) {
                log.error("Automatic registration failed!", e);
            }
        }
    }

    /**
     * Returns the id of this zone 
     *
     * @return the zoneId
     */
    public String getZoneId() {
        return (this.zoneID);
    }

    /**
     * Registers the agent with the ZIS
     *  
     * @return SIFAck response from ZIS
     * @throws Exception
     */
    public SIFAck register() throws Exception {
        return (this.sendMessage(new SIFRegister()).getAck());
    }

    /** 
     * Unregisters the agent with the ZIS. 
     * @return SIFAck response from ZIS
     * @throws Exception
     */
    public SIFAck unregister() throws Exception {
        return (this.sendMessage(new SIFUnregister()).getAck());
    }

    /**
     * Asks the ZIS server for its Zone Status
     * @return a SIFZoneStatus object
     * @throws Exception
     */
    public SIFZoneStatus getSIFZoneStatus() throws Exception {
        log.debug("sending zonestatus query");
        com.ozi.sif.objects.SIFQuery query = new com.ozi.sif.objects.SIFQuery();
        com.ozi.sif.objects.SIFQueryObject qo = new com.ozi.sif.objects.SIFQueryObject();
        qo.setObjectName("SIF_ZoneStatus");
        query.setSIFQueryObject(qo);

        SIFRequest req = new SIFRequest();
        req.setQuery(query);

        SIFAck ack = sendMessage(req).getAck();

        if (ack.getStatusCode() == Codes.Status.SUCCESS) {
            log.debug("successful ack response from zis");
            log.debug("sending gratis getmessage");

            ack = getMessage();
            SIFResponse resp = ack.getResponse();

            if (resp == null) {
                return (null);
            }

            SIFZoneStatus zs = new SIFZoneStatus();
            zs.setSIFObject(resp.getObjectData().getSIFZoneStatus().get(0));
            return (zs);
        }

        throw new Exception("protocol error");

    }

    // SYSTEM CONTROL MESSAGE
    /**
     * Notifies the receiver that the sender will not be able to handle new messages
     * <p>
     * Note: a wakeup or register message will notify the receiver that sender is once
     * again ready to receive messages
     *
     * @return SIFAck containing response from ZIS
     */
    public SIFAck sleep() throws Exception {
        log.debug("sending sleep");
        return (this.sendMessage(new SIFSleep()).getAck());
    }

    /**
     * Notifies receiver that sender is ready to receive messages
     *
     * @return SIFAck containing response from ZIS
     */
    public SIFAck wakeup() throws Exception {
        log.debug("sending wakeup");
        return (this.sendMessage(new SIFWakeup()).getAck());
    }

    /**
     * Checks whether the ZIS is alive and ready to receive messages 
     *
     * @return SIFAck containing response from ZIS
     */
    public SIFAck ping() throws Exception {
        log.debug("sending ping");
        return (this.sendMessage(new SIFPing()).getAck());
    }

    /**
     * Returns all messages currently in the ZIS for this agent
     *
     * @return SIFAck containing response from ZIS
     */
    public SIFAck getMessage() throws Exception {
        log.debug("sending generic sif message");
        return (this.sendMessage(new GetSIFMessage()).getAck());

    }

    /** 
     * Sends a <code>SIF_Provide</code> message to the ZIS 
     * @see SIFProvide
     * 
     * @param sifObjectName A valid SIF object name to which you would like to start providing events
     */
    public SIFAck sendProvide(String sifObjectName) throws Exception {
        log.debug("sending provide");
        SIFAck ack = null;
        SIFProvide prov = new SIFProvide();
        prov.provide(sifObjectName);
        ack = this.sendMessage(prov).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Provide</code> message to the ZIS 
     * @see SIFProvide
     * 
     * @param sifObjectName A list of valid SIF object names to which you would like to start providing events
     */
    public SIFAck sendProvide(List<String> sifObjectName) throws Exception {
        log.debug("sending provide");
        SIFAck ack = null;
        SIFProvide prov = new SIFProvide();
        prov.provide(sifObjectName);
        ack = this.sendMessage(prov).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Unprovide</code> message to the ZIS 
     * @see SIFUnprovide
     * 
     * @param sifObjectName A valid SIF object name to which you would like to stop providing events
     */
    public SIFAck sendUnprovide(String sifObjectName) throws Exception {
        log.debug("sending unprovide");
        SIFAck ack = null;
        SIFUnprovide unprov = new SIFUnprovide();
        unprov.unprovide(sifObjectName);
        ack = this.sendMessage(unprov).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Unprovide</code> message to the ZIS 
     * @see SIFUnprovide
     * 
     * @param sifObjectName A list of valid SIF object names to which you would like to stop providing events
     */
    public SIFAck sendUnprovide(List<String> sifObjectName) throws Exception {
        log.debug("sending unprovide");
        SIFAck ack = null;
        SIFUnprovide unprov = new SIFUnprovide();
        unprov.unprovide(sifObjectName);
        ack = this.sendMessage(unprov).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Subscribe</code> message to the ZIS 
     * @see SIFSubscribe
     * 
     * @param sifObjectName A valid SIF object name to which you would like to subscribe
     */
    public SIFAck sendSubscribe(String sifObjectName) throws Exception {
        log.debug("sending subscribe");
        SIFAck ack = null;
        SIFSubscribe sub = new SIFSubscribe();
        sub.subscribe(sifObjectName);
        ack = this.sendMessage(sub).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Subscribe</code> message to the ZIS 
     * @see SIFSubscribe
     * 
     * @param sifObjectName A valid SIF object name to which you would like to subscribe
     */
    public SIFAck sendSubscribe(List<String> sifObjectName) throws Exception {
        log.debug("sending subscribe");
        SIFAck ack = null;
        SIFSubscribe sub = new SIFSubscribe();
        sub.subscribe(sifObjectName);
        ack = this.sendMessage(sub).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Unsubscribe</code> message to the ZIS 
     * @see SIFUnsubscribe
     * 
     * @param sifObjectName A valid SIF object name to which you would like to unsubscribe
     */
    public SIFAck sendUnsubscribe(String sifObjectName) throws Exception {
        log.debug("sending unsubscribe");
        SIFAck ack = null;
        SIFUnsubscribe unsub = new SIFUnsubscribe();
        unsub.unsubscribe(sifObjectName);
        ack = this.sendMessage(unsub).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Unsubscribe</code> message to the ZIS 
     * @see SIFUnsubscribe
     * 
     * @param sifObjectName A valid SIF object name to which you would like to unsubscribe
     */
    public SIFAck sendUnsubscribe(List<String> sifObjectName) throws Exception {
        log.debug("sending unsubscribe");
        SIFAck ack = null;
        SIFUnsubscribe unsub = new SIFUnsubscribe();
        unsub.unsubscribe(sifObjectName);
        ack = this.sendMessage(unsub).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Request</code> message to the ZIS 
     * @see SIFRequest
     * 
     * @param query	
     * @param queryObj
     * @param sifObjectName A valid SIF object name for which you would like to make a request
     */
    public SIFAck sendRequest(com.ozi.sif.objects.SIFQuery query, com.ozi.sif.objects.SIFQueryObject queryObj, String sifObjectName) throws Exception {
        log.debug("sending request");
        SIFAck ack = null;

        queryObj.setObjectName(sifObjectName);
        query.setSIFQueryObject(queryObj);
        SIFRequest req = new SIFRequest();
        req.setQuery(query);

        ack = this.sendMessage(req).getAck();
        return (ack);
    }

    /** 
     * Sends a <code>SIF_Event</code> message to the ZIS 
     * @see SIFEvent
     * 
     * @param sifObject the SIF object for the event
     * @param action the SIFEvent action type
     * 
     * 
     */
    public SIFAck sendEvent(SIFObject sifObject, OZIObjectNames name, SIFEvent.Action action) throws Exception {
        log.debug("sending event");
        SIFAck ack = null;
        SIFEvent event = new SIFEvent();
        event.setEventData(sifObject, name.name(), action);
        ack = this.sendMessage(event).getAck();
        return (ack);
    }

    /**
     * Sends a complete <code>SIF_Response</code> message to the ZIS
     * @see SIFResponse
     * @param sifObject the object data to be returned (if applicable)
     * @param requestMsgID the id of the request that triggered this response
     * @param packetNumber the number of the current packet in the entire series
     * @param hasMorePackets true if this is NOT the last packet of the response series
     * @return a SIFAck
     * @throws Exception
     */
    public SIFAck sendResponse(SIFObject sifObject, String requestMsgID, int packetNumber, boolean hasMorePackets) throws Exception {
        SIFAck ack = null;
        SIFResponse resp = new SIFResponse(requestMsgID, packetNumber, hasMorePackets);
        resp.setObjectData(sifObject);
        ack = this.sendMessage(resp).getAck();
        return (ack);
    }

    /**
     * Sends a <code>SIF_Response</code> containing a <code>SIF_ERROR</code> message
     *  //TODO clean jdocs up
     * @param errorCode
     * @param errorCategory
     * @param errorDesc
     * @param extDesc
     * @param requestMsgID
     * @param packetNumber
     * @param hasMorePackets
     * @return
     * @throws Exception
     */
    public SIFAck sendResponse(int errorCode, int errorCategory, String errorDesc, String extDesc, String requestMsgID, int packetNumber, boolean hasMorePackets) throws Exception {
        log.debug("sending response");
        SIFAck ack = null;
        SIFResponse resp = new SIFResponse(requestMsgID, packetNumber, hasMorePackets);
        resp.setError(errorCode, errorCategory, errorDesc, extDesc);
        ack = this.sendMessage(resp).getAck();
        return (ack);
    }
}
